﻿using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.Threading;

namespace Redis.Library
{
    /// <summary>
    /// Redis连接池操作
    /// </summary>
    public class RedisConnectPool : IDisposable
    {

        private static object locker = new object();

        private int curConnectionPos;

        //是否释放连接池资源
        private readonly bool isDisposed = false;

        //连接池列表
        private List<ConnectionMultiplexer> pool = new List<ConnectionMultiplexer>();

        //连接池大小
        public int PoolSize
        {
            get
            {
                return pool.Count;
            }
        }

        /// <summary>
        /// 添加链接到连接池(加双锁)
        /// </summary>
        /// <param name="connectionStr"></param>
        /// <param name="poolSize"></param>
        public RedisConnectPool(string connectionStr, int poolSize)
        {
            try
            {
                if (pool.Count == 0)
                {
                    lock (locker)
                    {
                        if (pool.Count == 0)
                        {
                            for (var i = 0; i < poolSize; i++)
                            {
                                var cnn = ConnectionMultiplexer.Connect(connectionStr);
                                pool.Add(cnn);
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            if (isDisposed)
                return;
            foreach (var cnn in pool)
                cnn.Close();
            pool = new List<ConnectionMultiplexer>();
        }

        /// <summary>
        /// 从连接池中取出一个连接
        /// </summary>
        /// <returns></returns>
        public ConnectionMultiplexer GetConnection()
        {
            if (isDisposed)
                throw new Exception("这个池子已经被销毁了,请重新创建池子");
            var index = GetNextPos();
            var cnn = pool[index];
            return cnn.IsConnected ? cnn : FixConnection(index);
        }

        /// <summary>
        /// 修复pool中特定位置的连接
        /// </summary>
        /// <param name="index">有问题的connection位置</param>
        /// <returns></returns>
        private ConnectionMultiplexer FixConnection(int index)
        {
            // 从指定位置取出，判断是否被其它线程修复好了
            var cnn = pool[index];
            if (cnn.IsConnected)
                return cnn;
            var old = cnn;
            var config = old.Configuration;
            try
            {
                cnn = ConnectionMultiplexer.Connect(config);
            }
            catch (Exception ex)
            {

                throw new Exception(string.Format("重新建立连接（{0}）失败：{1}", config, ex.Message), ex);
            }
            finally {
                pool[index] = cnn;
                old.Close();
            }                
            return cnn;
        }

        /// <summary>
        /// 获得下一个连接的位置
        /// </summary>
        /// <returns></returns>
        private int GetNextPos()
        {
            // 优化逻辑，只有一个连接的情况下，直接返回第一个,减少不必要的运算
            if (PoolSize == 1)
                return 0;

            Interlocked.Add(ref curConnectionPos, 1);
            var next = curConnectionPos % PoolSize;
            return Math.Abs(next);
        }
    }
}
